import json
import math

import pymysql
import requests

from send_message import push_message

title = []  # 存取标题
long_alt = []  # 存取总数据
points = []  # 存取分类数据
original = []  # 存取高德标准数据，用于判断点线距离
min_distance = []  # 存取最短提醒距离


# 首先进行数据库读取，读取需要判断的风险区域，即is_risk为1的字段

def get_conn():
    conn = pymysql.connect(host='8.130.29.43', port=3306, user='root', passwd='zj20020427', db='BlueSpace',
                           charset='utf8')
    return conn


class Point:
    lng = ''
    lat = ''

    def __init__(self, lng, lat):
        self.lng = lng
        self.lat = lat


# 求外包矩形
def get_polygon_bounds(points):
    length = len(points)
    top = down = left = right = points[0]
    for i in range(1, length):
        if points[i].lng > top.lng:
            top = points[i]
        elif points[i].lng < down.lng:
            down = points[i]
        else:
            pass
        if points[i].lat > right.lat:
            right = points[i]
        elif points[i].lat < left.lat:
            left = points[i]
        else:
            pass
    top_left = Point(top.lng, left.lat)
    top_right = Point(top.lng, right.lat)
    down_right = Point(down.lng, right.lat)
    down_left = Point(down.lng, left.lat)
    return [top_left, top_right, down_right, down_left]


# 判断点是否在外包矩形外
def is_point_in_rect(point, polygon_bounds):
    top_left = polygon_bounds[0]
    top_right = polygon_bounds[1]
    down_right = polygon_bounds[2]
    down_left = polygon_bounds[3]
    return (down_left.lng <= point.lng <= top_right.lng
            and top_left.lat <= point.lat <= down_right.lat)


def is_point_in_polygon(point, points):
    polygon_bounds = get_polygon_bounds(points)
    if not is_point_in_rect(point, polygon_bounds):
        return False
    length = len(points)
    point_start = points[0]
    flag = False
    for i in range(1, length):
        point_end = points[i]
        # 点与多边形顶点重合
        if (point.lng == point_start.lng and point.lat == point_start.lat) or (
                point.lng == point_end.lng and point.lat == point_end.lat):
            return True
        # 判断线段两端点是否在射线两侧
        if (point_end.lat < point.lat <= point_start.lat) or (
                point_end.lat >= point.lat > point_start.lat):
            # 线段上与射线 Y 坐标相同的点的 X 坐标
            if point_end.lat == point_start.lat:
                x = (point_start.lng + point_end.lng) / 2
            else:
                x = point_end.lng - (point_end.lat - point.lat) * (
                        point_end.lng - point_start.lng) / (
                            point_end.lat - point_start.lat)
            # 点在多边形的边上
            if x == point.lng:
                return True
            # 射线穿过多边形的边界
            if x > point.lng:
                flag = not flag
            else:
                pass
        else:
            pass

        point_start = point_end
    return flag


def is_in_shape(input_lng, input_lat):
    for k in range(len(title)):
        polyline = points[k]
        pointsv = []
        for line in polyline:
            if line:
                try:
                    lng, lat = line.split(',')
                    pointsv.append(Point(float(lng), float(lat)))
                except ValueError:
                    pass
        if pointsv:
            result = is_point_in_polygon(Point(float(input_lng), float(input_lat)), pointsv)
            print('坐标{},{}在{}的核查结果{}：{}'.format(input_lng, input_lat, title[k], k, result))
            # for p in range(len(points[k])):
            #     # 取消注释此段可进行高德验证：https://lbs.amap.com/demo/jsapi-v2/example/relationship-judgment/point-surface-relation
            #     print("[{}],".format(points[k][p]))
            if result:  # 处于某区域时，返回区域名称
                return title[k]
    return "OK"


def cal_ang(point_1, point_2, point_3):
    """
    根据三点坐标计算夹角
    :param point_1: 点1坐标
    :param point_2: 点2坐标
    :param point_3: 点3坐标
    :return: 返回任意角的夹角值，这里只是返回点2的夹角
    """
    a = math.sqrt(
        (point_2[0] - point_3[0]) * (point_2[0] - point_3[0]) + (point_2[1] - point_3[1]) * (point_2[1] - point_3[1]))
    b = math.sqrt(
        (point_1[0] - point_3[0]) * (point_1[0] - point_3[0]) + (point_1[1] - point_3[1]) * (point_1[1] - point_3[1]))
    c = math.sqrt(
        (point_1[0] - point_2[0]) * (point_1[0] - point_2[0]) + (point_1[1] - point_2[1]) * (point_1[1] - point_2[1]))
    # print(a, b, c)
    A = math.degrees(math.acos((a * a - b * b - c * c) / (-2 * b * c)))
    B = math.degrees(math.acos((b * b - a * a - c * c) / (-2 * a * c)))
    C = math.degrees(math.acos((c * c - a * a - b * b) / (-2 * a * b)))
    return B


# 坐标转换
def trans_gis(longitude, latitude):
    # print("获取", longitude, latitude)
    url = "https://restapi.amap.com/v3/assistant/coordinate/convert?locations={},{}&coordsys=gps&key=e2043f0ab803f1d78e8372ee124630c9".format(
        longitude, latitude)
    # print(url)

    res = requests.get(url=url).text

    json_dict = json.loads(res)

    return json_dict['locations']


def sql_distance_up(title, distance, id):
    conn = get_conn()
    cur = conn.cursor()
    distance = round(distance)
    cur.execute("UPDATE {}_risk_district SET distance = '{}' WHERE risk_name = '{}' ".format(id, distance, title))
    print("距离已更新.")
    cur.close()
    conn.commit()


def get_distance(longitude, altitude, id):
    finally_distance_data = {}
    for v in range(len(original)):
        # print(v, "org=", original, "len=", len(original))
        distance_data = {}  # 临时存位置数据，当设备不处于某一预警区域时，将判断与哪个区域最近，以及距离，以改变risk预警阈值
        destination = "{},{}".format(longitude, altitude)
        url = "https://restapi.amap.com/v3/distance?origins={}&destination={}&type=0&output=json&key=e2043f0ab803f1d78e8372ee124630c9".format(
            original[v], destination)
        # print(url)  # url浏览器访问预览json
        data = json.loads(requests.get(url).text)
        for i in range(int(data['count'])):
            # print(data['results'][i]['distance'], data['results'][i]['origin_id'])
            distance_data[int(data['results'][i]['origin_id'])] = int(data['results'][i]['distance'])
        # print(distance_data)  # 完整字典
        minTwo = sorted(distance_data.items(), key=lambda kv: (kv[1], kv[0]))
        # print("距离传入坐标距离最小的两个点", minTwo[0], minTwo[1])  # 距离最小的两个点
        a1 = minTwo[0][0] - 1  # 高德返回id从1计数，需要-1
        a2 = minTwo[1][0] - 1
        a1_sp = points[v][a1].split(",")
        a2_sp = points[v][a2].split(",")
        # 下列输出可用于高德可视化观察角度，https://lbs.amap.com/demo/jsapi-v2/example/overlayers/polyline-draw
        # print("点:\n[{},{}],\n[{},{}],\n[{},{}]\n".format(longitude, altitude, a1_sp[0], a1_sp[1], a2_sp[0], a2_sp[1]))
        angle = cal_ang((float(longitude), float(altitude)), (float(a1_sp[0]), float(a1_sp[1])),
                        (float(a2_sp[0]), float(a2_sp[1])))
        if angle >= 90:  # 求出角度大于90度时，距离面的最短距离即为最短距离
            # print(title, minTwo, "V=", v)
            print("距离{}最短距离{}m(最短路径方法)".format(title[v], minTwo[0][1]))
            sql_distance_up(title[v], minTwo[0][1], id)
            finally_distance_data[title[v]] = minTwo[0][1]
        else:  # 锐角时，sin求直角边。即垂边。
            # https://lbs.amap.com/demo/jsapi-v2/example/overlay-editor/polylineeditor 多边形绘制器

            # 不可在数据库risk_dictrict中设置多边形”首尾相连“,否则出现等腰三角形时(精度m),角转换除数为0报错
            # print(minTwo[0][1], angle)
            math_distance = math.sin(math.radians(angle)) * minTwo[0][1]
            print("距离{}最短距离{}m(解三角形计算)".format(title[v], math_distance))
            sql_distance_up(title[v], math_distance, id)
            finally_distance_data[title[v]] = math_distance
    min_dis = sorted(finally_distance_data.items(), key=lambda x: (x[1], x[0]))
    #print(min_dis)
    # print(min_dis[0][1])  # , type(min_dis[0][1]))
    if min_dis[0][1] >= 1000:  # 距离此处500m以上时，赋小值
        Risk = 0.1
    elif min_dis[0][1] <= int(min_distance[v]):
        Risk = 0.92
        push_message.main("warning", "位置预警！&当前距离{}已到达预警阈值！".format(min_dis[0][0]), "all")
    else:
        #print(min_distance[v])
        #print("最小:", min_dis[0][1])
        Risk = 1 - min_dis[0][1] / 1000  # 仅在阈值范围~1000内求Risk
    return Risk


def get_risk(longitude, altitude, id):
    # 初始化
    long_alt.clear()
    title.clear()
    points.clear()
    original.clear()
    min_distance.clear()
    # 获取数据库连接
    conn = get_conn()
    cur = conn.cursor()
    cur.execute(f"SELECT * FROM {id}_risk_district where is_true='1'")
    for row in cur:  # 多次迭代,规范值
        # print("row", row, "I=", str(row[1]).split("|"))
        title.append(row[3])
        min_distance.append(row[4])
        v_long_alt = str(row[1]).split("|")
        long_alt.append(v_long_alt)
    # print("long:", long_alt)
    cur.close()
    conn.commit()

    for i in range(len(long_alt)):  # 迭代判断数量
        point_v = []
        point_v.clear()
        for v in range(len(long_alt[i])):
            point_v.append(long_alt[i][v].replace(" ", ""))
        # print("is:", point_v)
        # 分割经纬度坐标组
        original.append("|".join(point_v))
        points.append(point_v)

    print("预警区域:", title)

    if title:
        is_in_distance = is_in_shape(longitude, altitude)
        if is_in_distance == "OK":  # 判断是否位于预警区域内
            Risk = get_distance(longitude, altitude, id)  # 不在时，求其最短路径，改变risk阈值
            print(Risk)
        else:
            Risk = 0.99
            push_message.main("warning", "位置预警！&当前正处于{}预警范围内！".format(is_in_distance), "all")
        return Risk
    else:
        return 0.01  # 不存在预警区域时


# tr = str(trans_gis("106.521262345", "29.350299655")).split(",")
# print(get_risk(tr[0], tr[1], 1007))
